Basic4GL, Copyright (C) 2005 Tom Mulgrew

Network engine guide

2-Apr-2005
Tom Mulgrew

The Basic4GL network engine

The Basic4GL network engine is designed primarily for writing games.
It allows you to establish a network connection between two running Basic4GL programs and send blocks of data (which we call "messages") back and forth.
Basic4GL will attempt to send these messages as quickly as possible (with some extra logic for reliability and/or ordering if requested).

Actually that's all it really does.

Which leaves it up to you to decide what sort of data to send, what it means, and how often you send it. :)
However with a bit of planning it is possible to write some responsive multiplayer lan or internet games without too much fuss.

The network engine uses UDP/IP packets for communication.
Any network that can do TCP/IP can do UDP/IP (as TCP is built on top of UDP), so your programs can run over the Internet and TCP/IP local networks.

Basic4GL uses its own protocol for handling connection lifetime and reliable packet delivery which is optimised towards writing responsive networked games. It lets you choose which messages must get through and which ones don't matter if they get lost on the way. You can also choose which messages must arrive in the same order they were sent and which ones don't matter so much.
This upshot of this is that a carefully designed application will have the best chance to be able to continue smoothly if a data packet is lost in transmission, which is important for realtime games. (Unlike TCP/IP which has to stop for a few seconds if it hits an error).
The downside is because the Basic4GL network engine uses its own protocol, it can only talk to other applications using the same protocol. In other words, other Basic4GL programs, so you cannot use Basic4GL to write an FTP client, web browser, etc. (Standard TCP/IP support will likely be added to a later version.)

The current Basic4GL network engine currently supports:

Note: The network engine (at time of writing) is still young and lacks some features found in more mature networking engines likeautomatic bandwidth throttling.

Reading/writing messages with File I/O functions

The bulk of a program's network code usually involves:

A network message is a block of data, similar to a small disk file. Infact Basic4GL uses the file I/O functions to read and write the contents of network messages.
Instead of using OpenFileWrite and OpenFileRead, you use SendMessage and ReceiveMessage, but otherwise it's just like accessing a disk file.

Compare this program to write a simple text file:

dim file
file = OpenFileWrite ("files\test.txt") ' Open a file for output
WriteString (file, "Some text")         ' Write some text
CloseFile (file)                        ' Close the file

With this program to send a message over a network connection:

dim msg
...
msg = SendMessage (connection)          ' Create a message to send down connection
WriteString (msg, "Some text")          ' Write some text
CloseFile (msg)                         ' Send the message

(Note: The above program is incomplete...)

All of Basic4GL's file I/O functions except "OpenFileRead" and "OpenFileWrite" can be used with Basic4GL network messages.
These functions are described in the File I/O section of the Basic4GL Programmer's Guide.

Server-client connections

Two connect two computer over a network, you must do the following:

At this point both the client and the server have a "connection" with which they can send and receive data.
Data sent down the server's connection will be received by the client's connection and vice versa.

(Note: This can be extended to connect multiple computers together, by having one as the server and having the rest of them as clients that connect to the server. In this case the server will have multiple "connection"s, one for each client.)

Servers and server connections

NewServer

Format:

NewServer (port)

Where port is the port number on which the server "listen"s for connection requests.
NewServer() creates a server and returns a handle to identify the server to other functions (such as AcceptConnection()).

Example:

dim server
server = NewServer (8000)   ' Create a new server on port 8000

' ... Run the program

DeleteServer (server)       ' Close and delete the server

DeleteServer

Format:

DeleteServer (server)

Where server is a server handle returned from NewServer().
Shuts down and deletes the server. Any connections accepted by the server will automatically be disconnected and deleted.

It is good practice to close server objects (and connections) when finished with them.
If not closed explicitly, Basic4GL will automatically close them when the program ends.

ConnectionPending

Format:

ConnectionPending (server)

Where server is a server handle returned from NewServer().

ConnectionPending() returns true if a client has asked for a connection to the server and is waiting for the server to accept or reject it.
The connection can now be accepted with AcceptConnection() or rejected with RejectConnection().

AcceptConnection

Format:

AcceptConnection (server)

Where server is a server handle returned from NewServer().

AcceptConnection() accepts a pending connection request, creates a corresponding connection object and returns a handle for it.
If no connection is pending, AcceptConnection() does nothing and returns 0.

Example:

const port = 8000
dim server, connection

' Create server
server = NewServer (port)
printr "Server created. Waiting for connections"

' Wait for incoming connections
while true
    if ConnectionPending (server) then
        printr "Connection accepted"
    
        ' Accept connection
        connection = AcceptConnection (server)
        
        ' ... Do something here
        Sleep (1000)
        
        ' Close connection now that we're finished
        DeleteConnection (connection)
    endif
wend

RejectConnection

Format:

RejectConnection (server)

Where server is a server handle returned from NewServer().

Rejects an incoming connection request.
This returns a notification to the connecting client that the connection has not been accepted, and discards the request.

Client connections

NewConnection

Format:

NewConnection (address, port)

Creates a new connection and attempts to connect to a server at the specified address and port.
address is a text string specifying the network name to connect to. It can either be a DNS address (e.g. "someserver.com"), a numeric IP address (e.g. "192.168.0.1") or "localhost" (meaning connect to the same computer).
port is the port number. It must be the same one as the server is listening on, otherwise it wont find the server.

NewConnection() returns a handle identifying the connection that can be passed to other functions (such as SendMessage()).

DeleteConnection

Format:

DeleteConnection (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

Deletes a network connection.
If the connection is active, it will be closed, and a notification sent to the corresponding connection at the other end to inform it of the close.

Basic4GL also automatically closes and deletes any outstanding network connections when the program finishes.

Connection state

ConnectionConnected

Format:

ConnectionConnected (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

ConnectionConnected() returns true if the connection is still connected, or false if the connection has been disconnected.
Connections are considered "connected" when they are created, and remain that way until either:

ConnectionHandshaking

Format:

ConnectionHandshaking(connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

Returns true if the connection is in the hand-shaking state.

Connections created by NewConnection() are considered to be "hand-shaking" until the server accepts the connection (and the confirmation notification is received).
Once the connection is established, it leaves the hand-shaking state (ConnectionHandshaking() will then return false), and the connection is ready to send and receive messages.

Note: Server connections created with AcceptConnection() do not have a hand-shaking phase. For these ConnectionHandshaking() will always return false. The connection is fully established as soon as it has been accepted.

Sending messages

Data is passed through connections as "messages", variable length blocks of data which are transmitted and received as a single item.

SendMessage

Format:

SendMessage (connection, channel, reliable, smoothed)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

SendMessage() creates a message ready to be sent down connection, and returns a handle representing the message.
You can then pass this handle to the Write...() file I/O functions (WriteByte(), WriteString(), etc) to write data to the message, just as you would write data to a file. Refer to the file I/O functions in the Basic4GL Programmer's Guide for more information.

Once the message is ready, call CloseFile() to close the message and send it.

SendMessage() has 3 options which affect message delivery:

1. Channel

Channel is a "channel number" and affects the order in which messages are received.
Depending on network conditions messages can arrive at the receiving end in a different order than which they were sent. Also, in some cases a message (or part of a message) may be lost in transmission and have to be resent, which delays the message long enough for other messages to get in infront of it.
The Basic4GL network engine supports ordering of messages through "channels". Every connection has 32 channels (numbered 0 through 31 inclusive). Messages sent within a single channel are guaranteed to be received in the same order as they were sent, with the exception of channel # 0 which is the unordered channel.
Two messages sent down different channels are not guaranteed to be received in the same order.

There are multiple channels to allow you to specify on which messages the ordering is important. A good choice of channels can affect network performance, especially over unreliable networks (such as an internet connection). If an ordered message is delayed, the whole channel will stall until the message is received and slotted into its correct order. However other channels will still keep receive messages. So if a game was using on ordered channel for chat messages, and a different channel for position updates, the engine can keep receiving position updates even if a chat message is lost and must be re-transmitted.

2. Reliable

Reliable is true if the message must be delivered.
Depending on network conditions, some messages may be lost in transmission. The reliable flag specifies whether this is acceptable for this message (reliable = false) or whether the message must get through (in which case the network engine will keep resending the packet until delivery is confirmed).

3. Smoothed

The amount of time a message takes to reach its destination is called the network latency (or "lag"). This can vary greatly depending on the network connection. Over a local network the latency can be just a few hundredths of a second. Over a dial-up internet connection to the otherside of the world it can be as much as a second.
Depending on network conditions the latency can actually from message to message, meaning that messages (like position updates) that are sent out at nice regular spaced out intervals may arrive in at the other end in irregular clumps.

The Basic4GL network engine has a "smoothing" algorithm which compensates this by measuring the time it takes to deliver packets, and delaying early packets until they are considered "due".
So if un"smoothed" messages have a network latency of 100-200ms, applying smoothing might cause the majority of messages to have a latency of 180ms (with a few taking 180-200ms).

Be aware that this algorithm is effectively adding "lag" to faster messages, and should therefore be used with caution.

[Example here]

Receiving messages

MessagePending

Format:

MessagePending (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

MessagePending() returns true if a message has been received and can be fetched with ReceiveMessage().

MessageChannel

Format:

MessageChannel (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

MessageChannel() returns the channel number of the pending message. (See SendMessage() for more information).

MessageReliable

Format:

MessageReliable (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

MessageReliable() returns whether the pending message was sent as a reliable message (MessageReliable() = true) or as an unreliable message. (See SendMessage() for more information).

MessageSmoothed

Format:

MessageSmoothed (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

MessageSmoothed() returns whether the pending message was sent as a smoothed message (MessageSmoothed() = true) or not. (See SendMessage() for more information).

ReceiveMessage

Format:

ReceiveMessage (connection)

Where connection is a connection handle returned by NewConnection() or AcceptConnection().

ReceivedMessage() fetches the current pending message from the connection and returns a handle representing the message.
You can then pass this handle to the Read...() file I/O functions (ReadByte(), ReadChar(), etc) to read data from the message, just as you would read data from a file. The Seek() and EndOfFile() functions may also be used. Refer to the file I/O functions in the Basic4GL Programmer's Guide for more information.

Once you have finished with the message, you should discard it with CloseFile(), in order to free up resources.

[Example here]

Connection and handshaking flags

There are two flags which indicate the current connection state of a connection:

  1. Connected (see function: ConnectionConnected())
  2. Handshaking (see function: ConnectionHandshaking())

Client connections

When a client connection is created with NewConnection(), connected and handshaking are both set.
If the connection succeeds, connected remains set, and handshaking is cleared.
If the connection fails (either rejected by the server, or times out), connected is cleared. (handshaking may remain set though...)

Thus the code to establish a client connection might look something like this:

dim connection, address$, port

' Get connection details
print "Address?:": address$ = input$ ()
print "Port?:": port = val (input$ ())

' Attempt to connect to server
printr "Connecting..."
connection = NewConnection (address$, port)
while ConnectionConnected (connection) and ConnectionHandshaking (connection): wend

' Check if succeeded
if ConnectionConnected (connection) then
    printr "Connection succeeded"
    ' Do something with connection
    ' ...
else
    printr "Connection failed"
endif

' Close connection
DeleteConnection (connection)

If you attempt to use a connection while in the handshaking stage the network engine will do it's best to accomodate this. Specifically:

Server connections

When a server connection is created with AcceptConnection(), connected is set and handshaking is cleared.
The connection is considered established and can be used immediately.